package com.sjh.net

import androidx.lifecycle.Lifecycle
import com.google.gson.reflect.TypeToken
import com.sjh.baseutils.*
import com.sjh.baseutils.gson.GsonUtils
import com.sjh.baseutils.loading.LoadingType
import com.sjh.net.interceptor.HookInterceptor
import com.sjh.net.loading.LoadingDialogUtil
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.functions.Action
import io.reactivex.functions.Consumer
import io.reactivex.schedulers.Schedulers
import okhttp3.*
import retrofit2.Call
import retrofit2.HttpException
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.awaitResponse
import retrofit2.converter.scalars.ScalarsConverterFactory
import java.io.File
import java.io.IOException
import java.lang.ref.WeakReference
import java.util.ArrayList
import java.util.concurrent.TimeUnit

/**
 *
 * @author Extends
 * @date 2020/6/15
 */
open class EasyClient<T> {

    /**
     * 请求的域名
     */
    open var baseUrl : String? = null

    /**
     * 请求的url
     *
     * 如果是GET请求，则可以是绝对路径，也可以是相对路径
     * 如果是其他请求方式，则必须是相对路径
     */
    var url : String = ""

    /**
     * 请求方式，默认是GET请求
     */
    var method: Method = Method.GET

    /**
     *  header参数
     */
    var headers: HeaderMap.() -> Unit = {}

    /**
     * 表单传值或get传值时的参数
     */
    var params: ParamsMap.() -> Unit = {}

    /**
     * 待上传的文件
     */
    var files: FilesMap.() -> Unit = {}

    /**
     * json方式传值的参数
     * 该字段的优先级比params高
     */
    var jsonParams:String? = null

    /**
     * 当有返回值时的回调
     * @param source 服务器返回的原始数据
     * @param data 反序列化后的数据
     * @param isCache 返回的数据是否来自缓存
     * @param httpCode 服务器返回的HTTP CODE，大部分情况该值是200，但是小部分特殊情况下，如果后台要返回其他HTTP CODE 且
     *                 也返回了相应的数据，则依然会走onResult而不会回调onError
     */
    var onResult:((source:String,data:T,isCache:Boolean,httpCode:Int)->Unit)? = null

    /**
     * 当接口开始请求时回调
     * 注意：当上一个请求被主动终止后，当前请求不会再次调用onStart
     */
    var onStart:(()->Unit)?=null

    /**
     * 当接口请求结束时
     * 注意：当上一个请求被主动终止后，上一个请求的onEnd不会被调用
     */
    var onEnd:(()->Unit)?=null

    /**
     * 当接口请求出现异常时
     */
    var onError:((Throwable)->Unit)?=null

    /**
     * 请求类型
     *    STOP_LATER 已存在该请求时，不处理后续同样请求。
     *    STOP_BEFORE 已存在该请求时，停止以前的请求，开始最新的请求。
     *    NO_PROCESSING 不做特殊处理
     * 默认是 STOP_LATER
     */
    var requestType: RequestType =
        RequestType.STOP_LATER

    /**
     * 加载框
     */
    var loading: LoadingType = LoadingType.NONE

    /**
     * 缓存的数据，后台直接返回的，没有处理过的数据
     */
    var cache:String?=null

    /**
     * 缓存数据的使用方式：
     *    CACHE_NET 先加载缓存数据再加载网络数据。
     *    ONLY_CACHE 如果有缓存数据就不请求网络
     *
     * 默认是CACHE_NET
     */
    var useCacheType: UseCacheType =
        UseCacheType.CACHE_NET

    /**
     * 生命周期
     */
    var lifecycle: Lifecycle?=null

    /**
     * http的客户端
     */
    open var netClient: OkHttpClient? = null

    /**
     * 超时时间，单位毫秒
     * 默认值是30s
     *
     * 注意：如果netClient是单独设置的请求客户端，则该参数无效
     */
    open var connectTimeout:Long = 30*1000L

    /**
     * 拦截器
     * 如果给interceptor赋值为null，表示清除拦截器
     */
    var interceptor: InterceptorList.() -> Unit = {}

    /**
     * 重试次数
     *
     * 值为0表示不重试
     * 当值为n的时候，最大调用接口的次数是n+1次
     * 重试只会发生在IO异常时
     *
     */
    var retryCount:Int = 0

    /**
     * 用来确定泛型的类型
     */
    private lateinit var typeToken:TypeToken<T>

    /**
     * retrofit 实例
     */
    private lateinit var retrofit: Retrofit

    /**
     * 具体的headers值
     */
    private val headersMap : HeaderMap by lazy {
        val field = HeaderMap(defaultHeaders)
        field.headers()
        field
    }

    /**
     * 具体的params值
     */
    private val paramsMap : ParamsMap by lazy {
        val field = ParamsMap(defaultParams)
        field.params()
        field
    }

    /**
     * 具体的files值
     */
    private val filesMap : FilesMap by lazy {
        val field = FilesMap(defaultFiles)
        field.files()
        field
    }


    /**
     * 具体的拦截器
     */
    private val interceptorsList : InterceptorList by lazy {
        val field = InterceptorList(defaultInterceptors)
        field.interceptor()
        field
    }

    private val defaultHeaders: MutableMap<String,String> = mutableMapOf()
    private val defaultParams: MutableMap<String,String> = mutableMapOf()
    private val defaultFiles: MutableMap<String,File> = mutableMapOf()
    private val defaultInterceptors: ArrayList<Interceptor> = arrayListOf()


    /**
     * Debug模式时的一些配置，比如模拟数据返回
     */
    var debug:(DebugConfig.()->Unit)?=null

    /**
     * 使用协程同步请求
     * 部分特性无法使用
     */
    suspend fun sync(): Response<T> {
        //判断是否需要使用模拟header
        var debugConfig: DebugConfig?=null
        if(debug!=null){
            debugConfig = DebugConfig()
            debug?.let { debugConfig.it() }
            headersMap[HttpOptions.TestData] = debugConfig.result
        }

        if(netClient == null){
            netClient = OkHttpClient.Builder()
                .connectTimeout(connectTimeout,TimeUnit.MILLISECONDS)
                .callTimeout(connectTimeout,TimeUnit.MILLISECONDS)
                .readTimeout(connectTimeout,TimeUnit.MILLISECONDS)
                .writeTimeout(connectTimeout,TimeUnit.MILLISECONDS)
                .apply {
                    //添加进拦截器
                    interceptorsList.data.forEach {
                        addInterceptor(it)
                    }
                    if(headersMap.containsKey(HttpOptions.TestData)){
                        addInterceptor(HookInterceptor())
                    }
                }
                .build()
        }

        retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(netClient!!)
            .addConverterFactory(ScalarsConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()

        val call = startSync()

        var response:Response<String>? = null
        var currentCount = 0
        var tempException : Exception? = null

        var cloneCall : Call<String>? = null
        while (response == null && currentCount <= retryCount){
            try {
                cloneCall = call.clone()
                response = cloneCall.awaitResponse()
            }catch (e:Exception){
                response = null
                tempException = e
                currentCount ++
                e.printStackTrace()
            }finally {
                cloneCall?.cancel()
                cloneCall = null
            }
        }
        call.cancel()

        // 如果请求一直报错，则构造一个错误的Response返回
        if(response == null){
            val body = ResponseBody.create(null, tempException?.message ?: "未知异常")
            return Response.error(body, okhttp3.Response.Builder()
                .code(500)
                .message("Response.error()")
                .protocol(Protocol.HTTP_1_1)
                .request(Request.Builder().url("http://localhost/").build())
                .build())
        }

        // 请求正常，处理逻辑
        val body = if (!isString()) {
            GsonUtils.fromSafeJson(response.body()?:"", typeToken.type)
        } else {
            (response.body()?:"") as T
        }

        return if(response.isSuccessful){
            Response.success(body,response.raw())
        }else{
            Response.error<T>(response.errorBody(),response.raw())
        }
    }

    /**
     * 异步请求
     */
    fun asyn(): Disposable?{
        // 有缓存数据，先加载缓存数据
        val cacheResult = judgeCache()
        if(cacheResult){
            return null
        }

        val dialog = LoadingDialogUtil.getLoadingDialog(loading)
        // 是否终止了上一次请求
        var isDispose = false

        // 判断请求类型
        when(requestType){
            // 阻止之后的请求
            RequestType.STOP_LATER -> {
                //判断是否有存在正在进行的请求，如果有且isDisposed == false,则不重复进行请求
                val historyDisposable = NetManager.disposableMap[functionLable]?.get()
                if(historyDisposable != null && !historyDisposable.isDisposed){
                    "请求网络：过滤重复请求($functionLable)".loge()
                    return null
                }
            }

            // 取消之前的请求，开启之后的请求
            RequestType.STOP_BEFORE -> {
                //判断是否有存在正在进行的请求，如果有且isDisposed == false,则取消之前的请求开始新的请求
                val historyDisposable = NetManager.disposableMap[functionLable]?.get()
                if(historyDisposable != null && !historyDisposable.isDisposed){
                    "请求网络：取消未完成请求,开始新的请求($functionLable)".loge()
                    historyDisposable.dispose()
                    isDispose = true
                }
            }
            //不处理
            RequestType.NO_PROCESSING -> {

            }
        }

        //因为上一个请求被中途阻止了，所以上一个请求不会回调onEnd方法
        //为了避免onStart和onEnd不一一对应，所以这里不应该再调用onStart方法
        if(RequestType.STOP_BEFORE != requestType || !isDispose){
            //显示对话框
            if(dialog != null){
                NetManager.dialogMap[functionLable] = WeakReference(dialog)
                LoadingDialogUtil.showDialog(dialog)
            }
            //开始请求网络了
            onStart?.invoke()
        }else{
            if(dialog != null && !LoadingDialogUtil.isShowing(NetManager.dialogMap[functionLable]?.get())){
                NetManager.dialogMap[functionLable] = WeakReference(dialog)
                LoadingDialogUtil.showDialog(dialog)
            }
        }

        //判断是否需要使用模拟header
        var debugConfig: DebugConfig?=null
        if(debug!=null){
            debugConfig = DebugConfig()
            debug?.let { debugConfig.it() }
            headersMap[HttpOptions.TestData] = debugConfig.result
        }
        if(netClient == null){
            netClient = OkHttpClient.Builder()
                .connectTimeout(connectTimeout,TimeUnit.MILLISECONDS)
                .callTimeout(connectTimeout,TimeUnit.MILLISECONDS)
                .readTimeout(connectTimeout,TimeUnit.MILLISECONDS)
                .writeTimeout(connectTimeout,TimeUnit.MILLISECONDS)
                .apply {
                    //添加进拦截器
                    interceptorsList.data.forEach {
                        addInterceptor(it)
                    }
                    if(headersMap.containsKey(HttpOptions.TestData)){
                        addInterceptor(HookInterceptor())
                    }

                }
                .build()
        }

        retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(netClient!!)
            .addConverterFactory(ScalarsConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()

        val observable = startAsyn() .subscribeOn(Schedulers.io())
            .map {
                if (!isString()) {
                    it to GsonUtils.fromSafeJson(it, typeToken.type)
                } else {
                    it to (it as T)
                }
            }
            .observeOn(AndroidSchedulers.mainThread())
            .unsubscribeOn(Schedulers.io())
            .let {
                //重试机制
                if(retryCount > 0){
                    it.retryWhen(RetryWithDelay(retryCount))
                }else {
                    it
                }
            }


        // 结果回调
        val onNext = Consumer<Pair<String,T>>{
            onResult?.invoke(it.first,it.second,false,200)
        }

        // 完成时的回调
        val onCompleteCall = Action {
            //关闭弹窗
            LoadingDialogUtil.dimssDialog(dialog)
            onEnd?.invoke()
        }

        // 错误时的回调
        val onErrorCall = Consumer<Throwable> {

            // 单独对HttpException做一下处理
            // 默认情况下，okHttp认为httpCode不在[200,300)这个区间里，均认为是请求异常
            // 现定义如下：只要body里面有值，且反序列化没有问题，则认为是正常业务处理
            // 就算是 http code 不正确也会回调onResult，在该回调里面处理
            if(it is HttpException){
                var source:String? = null
                var data:T? = null
                val result = try {
                    it.response()?.errorBody()
                    source = if(it.response()?.isSuccessful == true)
                        it.response()?.body().toString()
                    else
                        it.response()?.errorBody()?.bytes()?.let {bs-> String(bs) }
                    data = if (!isString()) {
                        GsonUtils.fromSafeJson(source!!, typeToken.type)
                    } else {
                        (source as T)
                    }
                    true
                }catch (e:Exception){
                    false
                }

                if(result && data != null && source != null){
                    onResult?.invoke(source,data,false,it.code())
                    onCompleteCall.run()
                    return@Consumer
                }

            }

            it.message.loge()
            it.printStackTrace()
            //判断是否处理了onError回调
            //如果没有处理则执行默认处理
            if(onError == null){
                when (it) {
                    is HttpException -> "服务暂不可用"
                    is IOException -> "您的网络不给力，请稍后再试"
                    is NoNetworkException ->"您的网络不给力，请稍后再试"
                    else -> it.message+""
                }.toast()
            }else{
                onError!!.invoke(it)
            }
            //出错后调用onCompleteCall回调
            onCompleteCall.run()
        }


        // 判断是否对生命周期做了限制
        // 如果没有做限制，需要调用者自己处理请求的流程
        val disposable = if(lifecycle != null){
            observable.bindingLifecycle(lifecycle!!)
                .subscribe(onNext,onErrorCall,onCompleteCall)
        }else{
            observable.subscribe(onNext,onErrorCall,onCompleteCall)
        }
        //如果请求方式不是NO_PROCESSING，则保存请求记录
        if(RequestType.NO_PROCESSING != requestType){
            NetManager.disposableMap[functionLable] = WeakReference(disposable)
        }

        return disposable
    }

    /**
     * 判断泛型是否是字符串
     */
    private fun isString() = typeToken.rawType.simpleName == "String"

    /**
     * 方法签名
     */
    private val functionLable:String by lazy { "$lifecycle&$url&${paramsMap.keys.joinToString("_") { it }}" }

    /**
     * 初始化泛型类型，避免泛型擦除
     */
    fun initTypeToken(token: TypeToken<T>){
        typeToken = token
    }

    /**
     * 判断缓存
     * @return true  直接结束网络请求
     *         false 继续向下请求网络
     */
    private fun judgeCache():Boolean{
        if(!cache.isNullOrBlank()){
            val cacheData = try {
                if (!isString()) {
                    GsonUtils.fromSafeJson<T>(cache!!, typeToken.type)
                } else {
                    cache as? T?
                }
            }catch (e:Exception){
                e.printStackTrace()
                null
            }
            //如果反序列化失败了则不走缓存
            if(cacheData != null){
                "请求网络使用了缓存数据".loge()
                onResult?.invoke(cache!!,cacheData,true,200)
                //如果使用缓存策略是只使用缓存，则终止后续操作
                if(UseCacheType.ONLY_CACHE == useCacheType){
                    return true
                }
            }
        }
        return false
    }

    /**
     * 判断具体使用什么请求方式
     *
     * 异步请求
     */
    private fun startAsyn(): Observable<String> {
        val api = retrofit.create(BaseAPI::class.java)
        val absUrl = if(url.startsWith("/")) url.substring(1) else url
        return when (method) {
            Method.GET -> {
                if((absUrl.startsWith("http:") || absUrl.startsWith("https:"))){
                    val params = paramsMap.map { it.key to it.value }.joinToString("&") { "${it.first}=${it.second}" }
                    val path = if(params.isNullOrEmpty()){
                        absUrl
                    }else if(absUrl.indexOf("?")>=0){
                        "$absUrl&$params"
                    }else{
                        "$absUrl?$params"
                    }
                    api.getApi(path, headersMap)
                }else{
                    api.getApi(absUrl, paramsMap, headersMap)
                }
            }
            Method.POST -> {
                // 判断具体的请求方式
                // 图片上传，当前只写了表单上传图片，如果需要其他方式上传，到时候在扩展
                if(filesMap.size != 0){
                    val newDatas = arrayListOf<MultipartBody.Part>()
                    newDatas.addAll(filesMap.map {
                        MultipartBody.Part.createFormData(String(it.key.toByteArray()), (it.value.name).toUrlEncode(), RequestBody.create(
                            MediaType.parse("multipart/form-data"), it.value))
                    })
                    newDatas.addAll(paramsMap.map {
                        MultipartBody.Part.createFormData(String(it.key.toByteArray()),it.value)
                    })
                    api.postFile(absUrl,newDatas, headersMap)
                }
                // 通过json数据上传数据
                else if(jsonParams != null){
                    api.postApi(absUrl, jsonParams!!,headersMap)
                }
                // 普通的表单上传
                else{
                    api.postApi(absUrl,paramsMap,headersMap)
                }
            }
            Method.DELETE -> api.deleteApi(absUrl,paramsMap,headersMap)
            Method.PUT -> {
                if(jsonParams != null){
                    api.putApi(absUrl,jsonParams!!,headersMap)
                }else{
                    api.putApi(absUrl,paramsMap,headersMap)
                }
            }
            Method.PATCH -> api.patchApi(absUrl,paramsMap,headersMap)
        }
    }

    /**
     * 判断具体使用什么请求方式
     *
     * 同步请求
     */
    private fun startSync(): Call<String> {
        val api = retrofit.create(SyncBaseAPI::class.java)
        val absUrl = if(url.startsWith("/")) url.substring(1) else url
        return when (method) {
            Method.GET -> {
                if((absUrl.startsWith("http:") || absUrl.startsWith("https:"))){
                    val params = paramsMap.map { it.key to it.value }.joinToString("&") { "${it.first}=${it.second}" }
                    val path = if(params.isNullOrEmpty()){
                        absUrl
                    }else if(absUrl.indexOf("?")>=0){
                        "$absUrl&$params"
                    }else{
                        "$absUrl?$params"
                    }
                    api.getApi(path, headersMap)
                }else{
                    api.getApi(absUrl, paramsMap, headersMap)
                }
            }
            Method.POST -> {
                // 判断具体的请求方式
                // 图片上传，当前只写了表单上传图片，如果需要其他方式上传，到时候在扩展
                if(filesMap.size != 0){
                    val newDatas = arrayListOf<MultipartBody.Part>()
                    newDatas.addAll(filesMap.map {
                        MultipartBody.Part.createFormData(String(it.key.toByteArray()), (it.value.name).toUrlEncode(), RequestBody.create(
                            MediaType.parse("multipart/form-data"), it.value))
                    })
                    newDatas.addAll(paramsMap.map {
                        MultipartBody.Part.createFormData(String(it.key.toByteArray()),it.value)
                    })
                    api.postFile(absUrl,newDatas, headersMap)
                }
                // 通过json数据上传数据
                else if(jsonParams != null){
                    api.postApi(absUrl, jsonParams!!,headersMap)
                }
                // 普通的表单上传
                else{
                    api.postApi(absUrl,paramsMap,headersMap)
                }
            }
            Method.DELETE -> api.deleteApi(absUrl,paramsMap,headersMap)
            Method.PUT -> {
                if(jsonParams != null){
                    api.putApi(absUrl,jsonParams!!,headersMap)
                }else{
                    api.putApi(absUrl,paramsMap,headersMap)
                }
            }
            Method.PATCH -> api.patchApi(absUrl,paramsMap,headersMap)
        }
    }

    /**
     * 设置默认的header
     * 该设置项，可以在headers里面被修改
     */
    fun setDefaultHeaders(headers:Map<String,String>){
        defaultHeaders.clear()
        defaultHeaders.putAll(headers)
    }

    /**
     * 设置默认的params
     * 该设置项，可以在params里面被修改
     */
    fun setDefaultParams(params:Map<String,String>){
        defaultParams.clear()
        defaultParams.putAll(params)
    }

    /**
     * 设置默认的interceptors
     * 该设置项，可以在interceptors里面被修改
     */
    fun setDefaultInterceptors(interceptors:List<Interceptor>){
        defaultInterceptors.clear()
        defaultInterceptors.addAll(interceptors)
    }

    /**
     * 设置默认的interceptors
     * 该设置项，可以在interceptors里面被修改
     */
    fun addDefaultInterceptors(interceptors:List<Interceptor>){
        defaultInterceptors.addAll(interceptors)
    }
}

/**
 * 用来简化map值的填写
 */
open class ClientMap<T>(data:MutableMap<String,T> = hashMapOf()):MutableMap<String,T> by data{
    /**
     * 简化map添加值
     */
    infix fun String.to(value: T?) {
        if(value != null){
            this@ClientMap[this] = value
        }
    }

    /**
     * 简化直接加载一个map的情况
     */
    fun map(map:Map<String,T>){
        this.putAll(map)
    }
}

/**
 * 网络请求过程中Header对应的实体类
 */
class HeaderMap(data:MutableMap<String,String> = hashMapOf()) : ClientMap<String>(data)

/**
 * 网络请求过程中请求参数对应的实体类
 */
class ParamsMap(data:MutableMap<String,String> = hashMapOf()) : ClientMap<String>(data)

/**
 * 上传过程中请求参数对应的文件实体类
 */
class FilesMap(data:MutableMap<String, File> = hashMapOf()) : ClientMap<File>(data)

/**
 * 用来简化拦截器添加而实现的一个类
 */
class InterceptorList(val data:ArrayList<Interceptor> = arrayListOf()){

    infix fun String.to(interceptor: Interceptor) {
        data.add(interceptor)
    }

    /**
     * 重载 +
     */
    operator fun Interceptor.unaryPlus(){
        data.add(this)
    }

    /**
     * 清空
     */
    fun clear(){
        data.clear()
    }
}

/**
 * 网络请求基础方法
 *
 * @param preBlock 在用户调用block之前调用的回调，可以在这里处理默认参数，比如添加通用的baseUrl，通用的拦截器，通用的header等
 * @param options  在用户调用block之后调用的回调，主要是用来强制设定一些值，避免用户传入的参数不安全
 * @param block    用户设置参数的回调
 */
inline fun <reified T> request(preBlock: EasyClient<T>.()->Unit = {}, options: EasyClient<T>.()->Unit = {}, block: EasyClient<T>.()->Unit){
    val token = object : TypeToken<T>() {}
    EasyClient<T>().apply{
        initTypeToken(token)
        preBlock()
        block()
        options()
    }.asyn()
}

/**
 * 网络请求基础方法
 *
 * @param preBlock 在用户调用block之前调用的回调，可以在这里处理默认参数，比如添加通用的baseUrl，通用的拦截器，通用的header等
 * @param options  在用户调用block之后调用的回调，主要是用来强制设定一些值，避免用户传入的参数不安全
 * @param block    用户设置参数的回调
 */
suspend inline fun <reified T> requestAwait(preBlock: EasyClient<T>.()->Unit = {}, options: EasyClient<T>.()->Unit = {}, block: EasyClient<T>.()->Unit):Response<T>{
    val token = object : TypeToken<T>() {}
    return EasyClient<T>().apply{
        initTypeToken(token)
        preBlock()
        block()
        options()
    }.sync()
}

/**
 * 模拟接口返回数据
 */
class DebugConfig{
    /**
     * 模拟返回的数据
     */
    var result:String=""

}

/**
 * 框架网络模块常量定义
 */
object HttpOptions{
    /**
     * 表示测试数据
     * 如果存在该值则不会实际进行网络请求
     */
    internal const val TestData = "::tset-data"
}